package org.awa.demo.opencv.sound;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.awa.demo.opencv.constant.CarTypeEnum;
import org.awa.demo.opencv.constant.LoeConstants;
import org.awa.demo.opencv.entity.TrafficLogEntity;
import org.awa.demo.opencv.gis.bean.BuildingOrder;
import org.awa.demo.opencv.gis.bean.Element;
import org.awa.demo.opencv.gis.bean.HorizontalElement;
import org.awa.demo.opencv.gis.bean.RoadOrder;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.grid.Grids;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.WKTReader;
import org.opengis.feature.simple.SimpleFeature;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author xxf
 */
@Slf4j
@Component
public class SoundUtil {

    /**
     * 等效声级根据距离衰减 计算 线声源
     *
     * @param distance
     * @param leqt
     * @return
     * @throws Exception
     */
    public Double calAttenuationLeptLine(double distance,double leqt){
        try{
            //因为计算认为为无限长线声源本身的声音。而衰减公式是两个距离之间的比值(r / r0)，故假设r0为0.01米，即1cm的距离
            return leqt - 10d * Math.log10(distance * 100);
        }catch (Exception e){
            e.printStackTrace();
            return 0d;
        }
    }

    /**
     * 根据交通数据计算等效声级 计算
     *
     * @param catNum
     * @param carAvg
     * @return
     * @throws Exception
     */
    public Double calLeptByTraffic(long catNum,double carAvg, CarTypeEnum carTypeEnum) {
        try{
            return LoeConstants.getLoe(carTypeEnum) + 10 * Math.log10(catNum / carAvg) - 16d;
        }catch (Exception e){
            e.printStackTrace();
            return 0d;
        }
    }

    /**
     * 噪声点位 叠加
     *
     * @param soundList
     * @return
     */
    public Double soundSuperposition(List<Double> soundList){
        double totalLeqt = 0d;
        for (int i = 0; i < soundList.size(); i++) {
            totalLeqt += Math.pow(10,0.1 * soundList.get(i));
        }
        return 10 * Math.log10(totalLeqt);
    }

    /**
     * 计算道路等效声级
     *
     * @param roadOrder
     * @param trafficLogEntity
     */
    public void calRoadLept(RoadOrder roadOrder, TrafficLogEntity trafficLogEntity){
        List<Double> soundList = new ArrayList<>();

        if(trafficLogEntity.getBigNum() != null && trafficLogEntity.getBigNum() > 0 && trafficLogEntity.getBigRate() != null && trafficLogEntity.getBigRate() > 0){
            Double bigLeqt = calLeptByTraffic(trafficLogEntity.getBigNum(),trafficLogEntity.getBigRate(),CarTypeEnum.BIG_CAR);
            if(bigLeqt != null && bigLeqt > 0){
                soundList.add(bigLeqt);
            }
        }

        if(trafficLogEntity.getMiddleNum() != null && trafficLogEntity.getMiddleNum() > 0 && trafficLogEntity.getMiddleRate() != null && trafficLogEntity.getMiddleRate() > 0){
            Double middleLeqt = calLeptByTraffic(trafficLogEntity.getMiddleNum(),trafficLogEntity.getMiddleRate(),CarTypeEnum.MIDDLE_CAR);
            if(middleLeqt != null && middleLeqt > 0){
                soundList.add(middleLeqt);
            }
        }

        if(trafficLogEntity.getSmallNum() != null && trafficLogEntity.getSmallRate() > 0 && trafficLogEntity.getSmallNum() != null && trafficLogEntity.getSmallRate() > 0){
            Double smallLeqt = calLeptByTraffic(trafficLogEntity.getSmallNum(),trafficLogEntity.getSmallRate(),CarTypeEnum.SMALL_CAR);
            if(smallLeqt != null && smallLeqt > 0){
                soundList.add(smallLeqt);
            }
        }
        Double leqt = soundSuperposition(soundList);
        roadOrder.setLeqt(leqt);
    }

    public void calBuildLeqtArr(BuildingOrder buildingOrder,int gridLength){
        try{
            //先拆分网格，确定类型，以及空间坐标

            //将MKT转换为GEO对象
            GeometryFactory geometryFactory = new GeometryFactory();
            WKTReader reader = new WKTReader(geometryFactory);
            MultiPolygon multiPolygon = (MultiPolygon) reader.read(buildingOrder.getTheGeom());
            buildingOrder.setMultiPolygon(multiPolygon);

            //获取建筑物周长，作为图片的长
            double length = multiPolygon.getLength();
            buildingOrder.setLength(length);

            //水平范围 确定范围（获取边界）
            Envelope envelopeInternal = multiPolygon.getEnvelopeInternal();

            //栅格化
            SimpleFeatureSource grid = Grids.createHexagonalGrid(new ReferencedEnvelope(
                    envelopeInternal.getMinX(),envelopeInternal.getMaxX(),
                    envelopeInternal.getMinY(),envelopeInternal.getMaxY(),
                    DefaultGeographicCRS.WGS84),gridLength);//WGS84 说是没用，看看效果再说
            SimpleFeatureIterator iterator = grid.getFeatures().features();

            List<HorizontalElement> horizontalElementList = new ArrayList<>();
            while (iterator.hasNext()){
                SimpleFeature feature = iterator.next();
                Geometry geometry = (Geometry) feature.getDefaultGeometry();
                if(geometry.intersects(multiPolygon)){
                    //判断相交，获取质心坐标，以及交线长度，存入水平栅格数组。水平栅格数组与高度配合形成空间栅格数组
                    HorizontalElement element = new HorizontalElement();
                    element.setGeom(geometry);

                    Geometry intersectGeom = geometry.intersection(multiPolygon);
                    Geometry differenceGeom = geometry.difference(multiPolygon);
                    //栅格内交线长度 = (相交部分周长 + 栅格未相交部分周长 - 栅格周长) / 2
                    element.setIntersectionLength((intersectGeom.getLength() + differenceGeom.getLength() - geometry.getLength()) / 2);
                    horizontalElementList.add(element);
                }
            }

            //重新排序
            horizontalElementList = sortHorizontal(horizontalElementList);

            //计算每个栅格与各道路的距离
            List<RoadOrder> roadList = buildingOrder.getRoadList();
            if(horizontalElementList.size() > 0 && roadList != null && roadList.size() > 0){
                for (int i = 0; i < horizontalElementList.size(); i++) {
                    HorizontalElement element = horizontalElementList.get(i);
                    Point center = element.getGeom().getInteriorPoint();
                    for (int j = 0; j < roadList.size(); j++) {
                        RoadOrder roadOrder = roadList.get(j);
                        MultiLineString roadPolygon = (MultiLineString) reader.read(roadOrder.getTheGeom());
                        roadOrder.setGeom(roadPolygon);

                        double distance = roadPolygon.distance(center);
                        element.getRoadDistanceHorizontal().put(roadOrder,distance);
                    }
                }
            }
            //扩展栅格为二维数组并确定距离
            int heightNum = (int)Math.ceil(buildingOrder.getHeight() / gridLength);

            Element[][] elementArr = new Element[heightNum][horizontalElementList.size()];
            for (int i = 0; i < heightNum; i++) {
                for(int j = 0;j < horizontalElementList.size();j++){
                    HorizontalElement horizontalElement = horizontalElementList.get(j);
                    Element element = new Element();
                    elementArr[i][j] = element;
                    Point point = horizontalElement.getGeom().getInteriorPoint();
                    element.setX(point.getX());
                    element.setY(point.getY());

                    element.setLength(horizontalElement.getIntersectionLength());
                    final double height = (i + 0.5) * gridLength;
                    element.setHeight(height);

                    List<Double> leqtListInRoad = new ArrayList<>();
                    horizontalElement.getRoadDistanceHorizontal().entrySet().forEach(entity -> {
                        double realDistance = Math.sqrt(Math.pow(entity.getValue(),2) + Math.pow(height,2));
                        log.info("real distance: {},{},{}",realDistance,entity.getValue(),height);
                        leqtListInRoad.add(calAttenuationLeptLine(realDistance,entity.getKey().getLeqt()));
                    });
                    element.setLeqt(soundSuperposition(leqtListInRoad));
                }
            }
            //完成栅格元素的计算
            log.info(" ------------------ leqt -----------------------");
            for (int i = 0; i < elementArr.length; i++) {
                Element[] elements = elementArr[i];
                StringBuilder sb = new StringBuilder();
                for (int j = 0; j < elements.length; j++) {
                    Element element = elements[j];
                    sb.append(String.format("[%s][%s]",element.getHeight(),element.getLeqt())).append("       ");
                }
                log.info(sb.toString());
            }

            buildingOrder.setElements(elementArr);
        }catch (Exception e){
            e.printStackTrace();
            buildingOrder.setElements(null);
        }
    }

    /**
     * 栅格列表做展开排序
     *
     * @param elementList
     * @return
     */
    private List<HorizontalElement> sortHorizontal(List<HorizontalElement> elementList){
        if(elementList == null || elementList.size() == 0){
            return new ArrayList<>();
        }

        List<HorizontalElement> newList = new ArrayList<>();

        //确定最左下角以及最右上角，以横轴权重最优
        int leftBelowIndex = 0;
        double leftBelowIndexX = elementList.get(0).getX();
        double leftBelowIndexY = elementList.get(0).getY();
        double rightAboveIndexX = elementList.get(0).getX();
        double rightAboveIndexY = elementList.get(0).getY();
        for (int i = 0; i < elementList.size(); i++) {
            HorizontalElement element = elementList.get(i);
            if(element.getX() < leftBelowIndexX || element.getY() < leftBelowIndexY){
                leftBelowIndex = i;
                leftBelowIndexX = element.getX();
                leftBelowIndexY = element.getY();
            }
            if(element.getX() > rightAboveIndexX || element.getY() > rightAboveIndexY){
                rightAboveIndexX = element.getX();
                rightAboveIndexY = element.getY();
            }
        }

        //以左下角起个头
        newList.add(elementList.remove(leftBelowIndex));

        boolean directionLeftToRight = true;//当前的方向参数
        int count = 0;
        while (count < 200 && elementList.size() > 0){
            count++;//防止死循环

            if(elementList.size() == 1){
                //只剩一个，直接放，结束本次排序
                newList.add(elementList.remove(0));
                break;
            }
            for (int i = newList.size() - 1; i >= 0; i--) {//反向遍历
                HorizontalElement currNewElement = newList.get(i);

                //遍历获取相交关系的元素,根据优先级取得优先级最高的元素
                //按朝向 优先级最高的元素 从左向右时优先级为 下 > 右 > 上 > 左 。 从右向左时优先级为 上 > 左 > 下 > 右

                int prefPosition = -1;
                int indexJ = -1;
                for (int j = 0; j < elementList.size(); j++) {
                    HorizontalElement adjacentElement = elementList.get(j);
                    //如果不相交，则直接对下一个节点进行判断
                    if(!currNewElement.getGeom().touches(adjacentElement.getGeom())){
                        continue;
                    }
                    //获取相对位置
                    int positionRelation = GisPositionUtil.getPositionRelation(currNewElement.getX(),currNewElement.getY(),adjacentElement.getX(),adjacentElement.getY());
                    //判断位置优先级是否更高，更高则替代
                    if(higherPriority(prefPosition,positionRelation,directionLeftToRight)){
                        prefPosition = positionRelation;
                        indexJ = j;
                    }
                    //如果已经是最高优先级了，后续不用比，直接选这个然后跳出。因为栅格位置是规律的，相邻一边只可能有一个栅格
                    if((directionLeftToRight && positionRelation == GisPositionUtil.RIGHT_DOWN )
                            || ( !directionLeftToRight && positionRelation == GisPositionUtil.LEFT_UP)){
                        break;
                    }
                }

                //判断有没有找到合适的相交栅格，有的话栅格放入新列表，同时老列表去除对应元素
                if(indexJ > 0){
                    HorizontalElement newElement = elementList.remove(indexJ);
                    newList.add(newElement);

                    //遍历到最右上角的话要转向了
                    if(newElement.getX() == rightAboveIndexX && newElement.getY() == rightAboveIndexY){
                        directionLeftToRight = false;
                    }
                }
            }

        }

        //如果因为死循环出的遍历，剩下的节点一股脑按顺序赋值给新队列
        if(elementList.size() > 0){
            newList.addAll(elementList);
        }

        return newList;
    }

    //按朝向 优先级最高的元素 从左向右时优先级为 右下 > 下 >右上 > 上 > 左下 > 左上 。 从右向左时优先级为 左上 > 上 > 左下 > 下 > 右上 > 右下
    private boolean higherPriority(int prefPosition,int newPosition,boolean directionLeftToRight){
        int weightPref = 0;
        int weightNew = 0;
        if(directionLeftToRight){//从左到右
            switch (prefPosition){
                case GisPositionUtil.RIGHT_DOWN: weightPref = 6;break;
                case GisPositionUtil.BELOW: weightPref = 5;break;
                case GisPositionUtil.RIGHT_UP: weightPref = 4;break;
                case GisPositionUtil.ABOVE: weightPref = 3;break;
                case GisPositionUtil.LEFT_DOWN: weightPref = 2;break;
                default: weightPref = 1;break;
            }
            switch (newPosition){
                case GisPositionUtil.RIGHT_DOWN: weightNew = 6;break;
                case GisPositionUtil.BELOW: weightNew = 5;break;
                case GisPositionUtil.RIGHT_UP: weightNew = 4;break;
                case GisPositionUtil.ABOVE: weightNew = 3;break;
                case GisPositionUtil.LEFT_DOWN: weightNew = 2;break;
                default: weightNew = 1;break;
            }
        }else{//从右到左
            switch (prefPosition){
                case GisPositionUtil.LEFT_UP: weightPref = 6;break;
                case GisPositionUtil.ABOVE: weightPref = 5;break;
                case GisPositionUtil.LEFT_DOWN: weightPref = 4;break;
                case GisPositionUtil.BELOW: weightPref = 3;break;
                case GisPositionUtil.RIGHT_UP: weightPref = 2;break;
                default: weightPref = 1;break;
            }
            switch (newPosition){
                case GisPositionUtil.LEFT_UP: weightNew = 6;break;
                case GisPositionUtil.ABOVE: weightNew = 5;break;
                case GisPositionUtil.LEFT_DOWN: weightNew = 4;break;
                case GisPositionUtil.BELOW: weightNew = 3;break;
                case GisPositionUtil.RIGHT_UP: weightNew = 2;break;
                default: weightNew = 1;break;
            }
        }
        return weightNew > weightPref;
    }


}
